// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// go:build js&&wasm 

package js

import "sync"

var (
	funcsMu    sync.Mutex
	funcs             = make(map[uint32]func(Value, []Value) any)
	nextFuncID uint32 = 1
)

// Func是一个包装好的go函数，可由JavaScript调用。
type Func struct {
	Value // 调用Go函数的JavaScript函数
	id    uint32
}

// FuncOf返回一个供JavaScript使用的函数。
// 
// 调用Go函数fn时使用JavaScript的“this”关键字的值和调用的
// 参数。调用的返回值是
// 根据ValueOf将Go函数映射回JavaScript的结果。
// 
// 从JavaScript调用wrapped Go函数将
// 暂停事件循环并生成一个新的goroutine。
// 从Go to JavaScript调用时触发的其他包装函数
// 在同一个goroutine上执行。
// 
// 因此，如果一个包装函数阻塞，JavaScript的事件循环
// 将被阻塞，直到该函数返回。因此，调用任何需要事件循环（如fetch（http.Client））的异步JavaScript 
// API将导致
// 立即死锁。因此，一个阻塞函数应该显式地启动一个
// 新的goroutine。
// 
// Func。当函数不再被调用时，必须调用Release来释放资源。
func FuncOf(fn func(this Value, args []Value) any) Func {
	funcsMu.Lock()
	id := nextFuncID
	nextFuncID++
	funcs[id] = fn
	funcsMu.Unlock()
	return Func{
		id:    id,
		Value: jsGo.Call("_makeFuncWrapper", id),
	}
}

// Release释放分配给函数的资源。
// 调用Release后，不能调用该函数。
// 允许在函数仍在运行时调用Release。
func (c Func) Release() {
	funcsMu.Lock()
	delete(funcs, c.id)
	funcsMu.Unlock()
}

// setEventHandler在运行时包中定义。
func setEventHandler(fn func())

func init() {
	setEventHandler(handleEvent)
}

func handleEvent() {
	cb := jsGo.Get("_pendingEvent")
	if cb.IsNull() {
		return
	}
	jsGo.Set("_pendingEvent", Null())

	id := uint32(cb.Get("id").Int())
	if id == 0 { // 零表示死锁
		select {}
	}
	funcsMu.Lock()
	f, ok := funcs[id]
	funcsMu.Unlock()
	if !ok {
		Global().Get("console").Call("error", "call to released function")
		return
	}

	this := cb.Get("this")
	argsObj := cb.Get("args")
	args := make([]Value, argsObj.Length())
	for i := range args {
		args[i] = argsObj.Index(i)
	}
	result := f(this, args)
	cb.Set("result", result)
}
